/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.explorer.view; import java.awt.*; import java.awt.event.*; import java.awt.dnd.DnDConstants; import java.beans.*; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.util.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.tree.*; import org.openide.awt.MouseUtils; import org.openide.explorer.*; import org.openide.util.*; import org.openide.util.actions.SystemAction; import org.openide.util.actions.Presenter; import org.openide.nodes.Node; import org.openide.nodes.NodeOp; import org.openide.nodes.Children; /** Tree view abstract class. * * @author Petr Hamernik, Ian Formanek, Jaroslav Tulach */ public abstract class TreeView extends JScrollPane { // // static fields // /** generated Serialized Version UID */ static final long serialVersionUID = -1639001987693376168L; /** How long it takes before collapsed nodes are released from the tree's cache */ private static final int TIME_TO_COLLAPSE = System.getProperty ("netbeans.debug.heap") != null ? 0 : 15000; /** Minimum width of this component. */ private static final int MIN_TREEVIEW_WIDTH = 400; /** Minimum height of this component. */ private static final int MIN_TREEVIEW_HEIGHT = 400; // // components // /** Main <code>JTree</code> component. */ transient protected JTree tree; /** model */ transient private NodeTreeModel treeModel; /** Explorer manager, valid when this view is showing */ transient private ExplorerManager manager; // Attributes /** not null if default action on nodes allowed */ transient ClickAdapter defaultActionListener; /** not null if popup menu enabled */ transient PopupAdapter popupListener; /** the most important listener (on four types of events */ transient TreePropertyListener managerListener = null; /** weak variation of the listener for property change on the explorer manager */ transient PropertyChangeListener wlpc; /** weak variation of the listener for vetoable change on the explorer manager */ transient VetoableChangeListener wlvc; /** true if drag support is active */ transient boolean dragActive = false; /** true if drop support is active */ transient boolean dropActive = false; /** Drag support */ transient TreeViewDragSupport dragSupport; /** Drop support */ transient TreeViewDropSupport dropSupport; /** Constructor. */ public TreeView () { this (true, true); } /** Constructor. * @param defaultAction should double click on a node open its default action? * @param popupAllowed should right-click open popup? */ public TreeView (boolean defaultAction, boolean popupAllowed) { initializeTree (); // hack - DnD not stable now... // setDragSource(true); // setDropTarget(true); setPopupAllowed (defaultAction); setDefaultActionAllowed (popupAllowed); } /** Initializes the tree & model. */ private void initializeTree () { // initilizes the JTree treeModel = createModel (); tree = new AutoscrollJTree(treeModel) { public void updateUI () { setCellRenderer(new NodeRenderer ()); super.updateUI (); } }; setViewportView (tree); NodeRenderer rend = NodeRenderer.sharedInstance (); tree.setCellRenderer(rend); tree.setCellEditor(new TreeViewCellEditor(tree, new NodeRenderer.Tree ())); tree.putClientProperty("JTree.lineStyle", "Angled"); // NOI18N tree.setEditable(true); ToolTipManager.sharedInstance().registerComponent(tree); // init listener & attach it to closing of managerListener = new TreePropertyListener(); tree.addTreeExpansionListener (managerListener); // do not care about focus setRequestFocusEnabled (false); } /** Is it permitted to display a popup menu? * @return <code>true</code> if so */ public boolean isPopupAllowed () { return popupListener != null; } /** Enable/disable displaying popup menus on tree view items. * Default is enabled. * @param value <code>true</code> to enable */ public void setPopupAllowed (boolean value) { if (popupListener == null && value) { // on popupListener = new PopupAdapter (); tree.addMouseListener (popupListener); return; } if (popupListener != null && !value) { // off tree.removeMouseListener (popupListener); popupListener = null; return; } } /** Does a double click invoke the default node action? * @return <code>true</code> if so */ public boolean isDefaultActionEnabled () { return defaultActionListener != null; } /** Also requests focus for the tree component */ public void requestFocus () { super.requestFocus(); tree.requestFocus(); } /** Enable/disable double click to invoke default action. * @param value <code>true</code> to enable */ public void setDefaultActionAllowed (boolean value) { if (defaultActionListener == null && value) { // on defaultActionListener = new ClickAdapter (); tree.addMouseListener (defaultActionListener); tree.registerKeyboardAction( defaultActionListener, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), JComponent.WHEN_FOCUSED ); return; } if (defaultActionListener != null && !value) { // off tree.removeMouseListener (defaultActionListener); tree.unregisterKeyboardAction( KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false) ); defaultActionListener = null; return; } } /** * Is the root node of the tree displayed? * * @return <code>true</code> if so */ public boolean isRootVisible() { return tree.isRootVisible(); } /** Set whether or not the root node from * the <code>TreeModel</code> is visible. * * @param rootVisible <code>true</code> if it is to be displayed * @beaninfo * bound: true * description: Whether or not the root node * from the TreeModel is visible. */ public void setRootVisible (boolean visible) { tree.setRootVisible (visible); } /********** Support for the Drag & Drop operations *********/ /** @return true if dragging from the view is enabled, false * otherwise.<br> * Drag support is disabled by default. */ public boolean isDragSource () { return dragActive; } /** Enables/disables dragging support. * @param state true enables dragging support, false disables it. */ public void setDragSource (boolean state) { if (state == dragActive) return; dragActive = state; // create drag support if needed if (dragActive && (dragSupport == null)) dragSupport = new TreeViewDragSupport(this, tree); // activate / deactivate support according to the state dragSupport.activate(dragActive); } /** @return true if dropping to the view is enabled, false * otherwise<br> * Drop support is disabled by default. */ public boolean isDropTarget () { return dropActive; } /** Enables/disables dropping support. * @param state true means drops into view are allowed, * false forbids any drops into this view. */ public void setDropTarget (boolean state) { if (state == dropActive) return; dropActive = state; // create drop support if needed if (dropActive && (dropSupport == null)) dropSupport = new TreeViewDropSupport(this, tree); // activate / deactivate support according to the state dropSupport.activate(dropActive); } /** @return Set of actions which are allowed when dragging from * asociated component. * Actions constants comes from DnDConstants.XXX constants. * All actions (copy, move, link) are allowed by default. */ public int getAllowedDragActions () { // PENDING return DnDConstants.ACTION_MOVE | DnDConstants.ACTION_COPY | DnDConstants.ACTION_LINK; } /** Sets allowed actions for dragging * @param actions new drag actions, using DnDConstants.XXX */ public void setAllowedDragActions (int actions) { // PENDING } /** @return Set of actions which are allowed when dropping * into the asociated component. * Actions constants comes from DnDConstants.XXX constants. * All actions are allowed by default. */ public int getAllowedDropActions () { // PENDING return DnDConstants.ACTION_MOVE | DnDConstants.ACTION_COPY | DnDConstants.ACTION_LINK; } /** Sets allowed actions for dropping. * @param actions new allowed drop actions, using DnDConstants.XXX */ public void setAllowedDropActions (int actions) { // PENDING } // // Processing functions // /* Initializes the component. */ public void addNotify () { super.addNotify (); // Enter key in the tree ExplorerManager newManager = ExplorerManager.find (TreeView.this); if (newManager != manager) { if (manager != null) { manager.removeVetoableChangeListener (wlvc); manager.removePropertyChangeListener (wlpc); } manager = newManager; manager.addVetoableChangeListener(wlvc = WeakListener.vetoableChange (managerListener, manager)); manager.addPropertyChangeListener(wlpc = WeakListener.propertyChange (managerListener, manager)); synchronizeRootContext (); synchronizeExploredContext (); synchronizeSelectedNodes (); } tree.getSelectionModel().addTreeSelectionListener(managerListener); } /* Deinitializes listeners. */ public void removeNotify () { super.removeNotify (); //System.out.println ("Calling remove notify..."); // NOI18N tree.getSelectionModel().removeTreeSelectionListener(managerListener); } /* Defines new way how to compute preffered size */ public Dimension getPreferredSize() { Dimension dim = super.getPreferredSize(); if (dim.width < MIN_TREEVIEW_WIDTH) dim.width = MIN_TREEVIEW_WIDTH; if (dim.height < MIN_TREEVIEW_HEIGHT) dim.height = MIN_TREEVIEW_HEIGHT; return dim; } // ************************************* // Methods to be overriden by subclasses // ************************************* /** Allows subclasses to provide own model for displaying nodes. * @return the model to use for this view */ protected abstract NodeTreeModel createModel(); /** Called to allow subclasses to define the behaviour when a * node(s) are selected in the tree. * * @param nodes the selected nodes * @param em explorer manager to work on (change nodes to it) * @throws PropertyVetoException if the change cannot be done by the explorer * (the exception is silently consumed) */ protected abstract void selectionChanged (Node[] nodes, ExplorerManager em) throws PropertyVetoException; /** Called when explorer manager is about to change the current selection. * The view can forbid the change if it is not able to display such * selection. * * @param nodes the nodes to select * @return false if the view is not able to change the selection */ protected abstract boolean selectionAccept (Node[] nodes); /** Show a given path in the screen. It depends on the kind of <code>TreeView</code> * if the path should be expanded or just made visible. * * @param path the path */ protected abstract void showPath(TreePath path); /** Shows selection to reflect the current state of the selection in the explorer. * * @param paths array of paths that should be selected */ protected abstract void showSelection (TreePath[] paths); /** Should a context menu of the explored context be used? * Applicable when no nodes are selected and the user wants to invoke * a context menu (clicks right mouse button). * * @return <code>true</code> if so; <code>false</code> in the default implementation */ protected boolean useExploredContextMenu() { return false; } // // synchronizations // /** Called when selection in tree is changed. */ final void callSelectionChanged (Node[] nodes) { manager.removePropertyChangeListener (wlpc); manager.removeVetoableChangeListener (wlvc); try { selectionChanged (nodes, manager); } catch (PropertyVetoException e) { synchronizeSelectedNodes (); } finally { manager.addPropertyChangeListener (wlpc); manager.addVetoableChangeListener (wlvc); } } /** Synchronize the root context from the manager of this Explorer. */ final void synchronizeRootContext() { treeModel.setNode (manager.getRootContext ()); } /** Synchronize the explored context from the manager of this Explorer. */ final void synchronizeExploredContext() { TreePath treePath = new TreePath (treeModel.getPathToRoot (VisualizerNode.getVisualizer (null, manager.getExploredContext ()))); showPath(treePath); } /** Synchronize the selected nodes from the manager of this Explorer. * The default implementation does nothing. */ final void synchronizeSelectedNodes() { Node[] arr = manager.getSelectedNodes (); TreePath[] paths = new TreePath[arr.length]; for (int i = 0; i < arr.length; i++) { TreePath treePath = new TreePath (treeModel.getPathToRoot (VisualizerNode.getVisualizer (null, arr[i]))); paths[i] = treePath; } tree.getSelectionModel().removeTreeSelectionListener(managerListener); showSelection (paths); tree.getSelectionModel().addTreeSelectionListener(managerListener); } /** Expands all paths. */ public void expandAll () { int i = 0, j, k = tree.getRowCount (); do { do { j = tree.getRowCount (); tree.expandRow (i); } while (j != tree.getRowCount ()); i++; } while (i < tree.getRowCount ()); } /** Listens to the property changes on tree */ private class TreePropertyListener implements VetoableChangeListener, PropertyChangeListener, TreeExpansionListener, TreeSelectionListener { public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { if (!selectionAccept ((Node[])evt.getNewValue ())) { throw new PropertyVetoException ("", evt); // NOI18N } } } public final void propertyChange(PropertyChangeEvent evt) { if (manager == null) return; // the tree view has been removed before the event got delivered if (evt.getPropertyName().equals(ExplorerManager.PROP_ROOT_CONTEXT)) synchronizeRootContext(); if (evt.getPropertyName().equals(ExplorerManager.PROP_EXPLORED_CONTEXT)) synchronizeExploredContext(); if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) synchronizeSelectedNodes(); } public void treeExpanded (TreeExpansionEvent ev) { } public void treeCollapsed (final TreeExpansionEvent ev) { RequestProcessor.postRequest (new Runnable () { public void run () { SwingUtilities.invokeLater (new Runnable () { public void run () { boolean open = tree.isExpanded (ev.getPath ()); NodeTreeModel t = treeModel; if (!open && t != null) { //System.out.println(" collapsing " + ev.getPath () + ":" + tree.isCollapsed( ev.getPath () ) ); // NOI18N t.nodeStructureChanged( (TreeNode) ev.getPath().getLastPathComponent() ); //System.out.println(" AND NOW " + ev.getPath () + ":" + tree.isCollapsed( ev.getPath () ) ); // NOI18N } } }); } }, TIME_TO_COLLAPSE); } /* Called whenever the value of the selection changes. * @param ev the event that characterizes the change. */ public void valueChanged(TreeSelectionEvent ev) { LinkedList ll = new LinkedList (); TreePath[] paths = tree.getSelectionPaths (); if (paths == null) { callSelectionChanged (new Node[0]); } else { for (int i = 0; i < paths.length; i++) { ll.add (Visualizer.findNode (paths[i].getLastPathComponent ())); } callSelectionChanged ((Node[])ll.toArray (new Node[ll.size ()])); } } } // end of TreePropertyListener /** Popup adapter. */ private class PopupAdapter extends MouseUtils.PopupMouseAdapter { protected void showPopup (MouseEvent e) { int selRow = tree.getRowForLocation(e.getX(), e.getY()); if (!tree.isRowSelected(selRow)) { tree.setSelectionRow(selRow); } if (selRow != -1) { JPopupMenu popup = NodeOp.findContextMenu(manager.getSelectedNodes()); if ((popup != null) && (popup.getSubElements().length > 0)) { java.awt.Point p = getViewport().getViewPosition(); p.x = e.getX() - p.x; p.y = e.getY() - p.y; popup.show(TreeView.this, p.x, p.y); } } } } /** clicking adapter */ private class ClickAdapter extends MouseAdapter implements ActionListener { public void mouseClicked(MouseEvent e) { int selRow = tree.getRowForLocation(e.getX(), e.getY()); //Default action if ((selRow != -1) && SwingUtilities.isLeftMouseButton(e)) { TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); Node node = Visualizer.findNode (selPath.getLastPathComponent()); if (defaultActionListener != null && MouseUtils.isDoubleClick(e)) { //System.out.println ("Double clicked???"); // NOI18N SystemAction sa = node.getDefaultAction (); if (sa != null) { sa.actionPerformed (new ActionEvent ( node, ActionEvent.ACTION_PERFORMED, "" // NOI18N )); e.consume (); } else { // this is to be uncommented if we can force // JTree not to expand / collapse on double click Package p = Package.getPackage ("java.lang"); // NOI18N if (p.isCompatibleWith("1.3")) { // NOI18N if (tree.isExpanded(selRow)) tree.collapseRow(selRow); else tree.expandRow(selRow); } } } } } public void actionPerformed(ActionEvent evt) { Node[] nodes = manager.getSelectedNodes(); if (nodes.length == 1) { SystemAction sa = nodes[0].getDefaultAction (); if (sa != null) { sa.actionPerformed (new ActionEvent ( nodes[0], ActionEvent.ACTION_PERFORMED, "")); // NOI18N } } } } } /* * Log * 41 Gandalf 1.40 3/11/00 Martin Ryzl menufix [by E.Adams, * I.Formanek] * 40 Gandalf 1.39 1/16/00 Ian Formanek Fixed last change * 39 Gandalf 1.38 1/16/00 Ian Formanek Removed semicolons after * methods body to prevent fastjavac from complaining * 38 Gandalf 1.37 1/13/00 Ian Formanek NOI18N * 37 Gandalf 1.36 1/12/00 Ian Formanek NOI18N * 36 Gandalf 1.35 1/7/00 Jaroslav Tulach #5160, but works * correctly only on JDK1.3 on JDK1.2 does both, expands the node and also * starts default action. alas. * 35 Gandalf 1.34 12/9/99 Jaroslav Tulach Double-click does only * one action (expand/invoke default). * 34 Gandalf 1.33 11/5/99 Jaroslav Tulach WeakListener has now * registration methods. * 33 Gandalf 1.32 11/3/99 Ian Formanek Fixed bug 4616 - * packages are automaticly renamed by name of last selected file * 32 Gandalf 1.31 10/29/99 Ian Formanek Removed dumpStack * 31 Gandalf 1.30 10/28/99 Ian Formanek Fixed bug #4603 - When * in-place renaming an item in tree, when the editing is finished by * clicking outside of the edit line, the item *should* be renamed to the * current text in the input line. * 30 Gandalf 1.29 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 29 Gandalf 1.28 10/7/99 David Simonek focus transferrring * added * 28 Gandalf 1.27 10/6/99 Petr Hamernik roll-back last change * 27 Gandalf 1.26 10/5/99 Petr Hamernik large model is set - * then FixedHeightCellCache is used instead of Variable... . (memory leak * prevention) * 26 Gandalf 1.25 9/24/99 Petr Hamernik fixed bug #3486 * 25 Gandalf 1.24 9/16/99 Petr Hrebejk Collapsing fixed + -D * netbeans.debug.heap for immediate tree collapsing added * 24 Gandalf 1.23 8/27/99 Jaroslav Tulach New threading model & * Children. * 23 Gandalf 1.22 7/16/99 Ian Formanek Fixed possible problems * with timing of closing/event listening * 22 Gandalf 1.21 7/16/99 Ales Novak new win sys * 21 Gandalf 1.20 6/28/99 Ian Formanek Fixed bug 2043 - It is * virtually impossible to choose lower items of New From Template from * popup menu on 1024x768 * 20 Gandalf 1.19 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 19 Gandalf 1.18 5/26/99 Ian Formanek Fixed ST problems * 18 Gandalf 1.17 5/17/99 Ian Formanek Fixed last change * 17 Gandalf 1.16 5/17/99 David Simonek DnD switched off :-( * 16 Gandalf 1.15 5/11/99 David Simonek addNotify now run under * Children.Mutex * 15 Gandalf 1.14 4/27/99 David Simonek autoscroll support and * visual feedback in DnD operations added * 14 Gandalf 1.13 4/21/99 David Simonek modified to support DnD * 13 Gandalf 1.12 4/16/99 Jan Jancura Object Browser support * 12 Gandalf 1.11 4/9/99 Ian Formanek Removed debug printlns * 11 Gandalf 1.10 4/1/99 David Simonek double click bug fixed * 10 Gandalf 1.9 3/22/99 David Simonek deregistering mouse * listeners * 9 Gandalf 1.8 3/20/99 Jesse Glick [JavaDoc] * 8 Gandalf 1.7 3/18/99 Petr Hamernik * 7 Gandalf 1.6 3/16/99 Petr Hamernik tooltip improvement * 6 Gandalf 1.5 3/15/99 Petr Hamernik * 5 Gandalf 1.4 3/4/99 Jan Jancura Localization moved * 4 Gandalf 1.3 2/11/99 Jaroslav Tulach SystemAction is * javax.swing.Action * 3 Gandalf 1.2 1/6/99 Jaroslav Tulach * 2 Gandalf 1.1 1/6/99 Ian Formanek Reflecting changes in * location of package "awt" * 1 Gandalf 1.0 1/5/99 Ian Formanek * $ */